﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.DirectoryServices;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    #endregion

    public class OUMethods {

        /// <summary>
        /// Creates an OU for the specified full path for the OU, recursively creating any parent levels.
        /// </summary>
        /// <remarks>May also be used to return a DirectoryEntry handle to an OU that exists</remarks>
        public DirectoryEntry CreateOU(string ouDN) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} OU DN: {3}",
                DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(),
                ouDN != null ? ouDN : "NULL"));

            #region Validation
            if (!string.IsNullOrWhiteSpace(ouDN) && !ouDN.StartsWith("OU=")) {
                throw new ApplicationException(string.Format("Incorrect OU DN Format: {0}", ouDN != null ? ouDN : "NULL"));
            }
            #endregion

            if (ADDataAccess.ObjectExists(ouDN)) {
                string adjustedBindPath = ADDataAccess.GetAdjustedBindPath(ouDN, PartitionScope.LocalPartition, forUpdate: true);
                var authenticationType = ADDataAccess.GetAdjustedAuthenticationType(adjustedBindPath);
                var directoryEntry = new DirectoryEntry(adjustedBindPath, null, null, authenticationType);
                
                return directoryEntry;
            }

            // get parent path
            string parentDN = ADDataAccess.GetParentDN(ouDN);

            DirectoryEntry parentDirectoryEntry = null;
            // if parent is not domain path and does not exist, create it
            if (parentDN.StartsWith("OU=", StringComparison.OrdinalIgnoreCase) && !ADDataAccess.ObjectExists(parentDN)) {
                parentDirectoryEntry = CreateOU(parentDN);
            }
            else {
                if (parentDN.StartsWith("OU=", StringComparison.OrdinalIgnoreCase) || parentDN.StartsWith("DC=", StringComparison.OrdinalIgnoreCase)) {
                    string adjustedBindPath = ADDataAccess.GetAdjustedBindPath(parentDN, PartitionScope.LocalPartition, forUpdate: true);
                    var authenticationType = ADDataAccess.GetAdjustedAuthenticationType(adjustedBindPath);

                    parentDirectoryEntry = new DirectoryEntry(adjustedBindPath, null, null, authenticationType);
                }
            }

            // get the OU name
            string ouName = ouDN.Substring("OU=".Length);
            if (ouName.ToUpper().Contains(",OU=")) {
                ouName = ouName.Substring(0, ouName.IndexOf(",OU=", StringComparison.OrdinalIgnoreCase));
            }
            else if (ouName.ToUpper().Contains(",DC=")) {
                ouName = ouName.Substring(0, ouName.IndexOf(",DC=", StringComparison.OrdinalIgnoreCase));
            }
            else {
                return null;
            }

            return CreateOU(ouName, parentDirectoryEntry);
        }

        /// <summary>
        /// Creates an OU container at the specified location
        /// </summary>
        public DirectoryEntry CreateOU(string name, DirectoryEntry parentDirectoryEntry) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} Name: {3} Parent location DN: {4}",
                DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(),
                name != null ? name : "NULL", parentDirectoryEntry != null ? parentDirectoryEntry.Path : "NULL"));

            if (name.StartsWith("OU=", StringComparison.OrdinalIgnoreCase)) {
                name = name.Substring("OU=".Length);
            }

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                ObjectName = name,
                ObjectDirectoryEntry = parentDirectoryEntry
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, DirectoryEntry>(
                ref methodParameters,
                (x) => {
                    methodParameters.ServerNameOrAdjustedBindPath = parentDirectoryEntry.Path;
                    var ouDirectoryEntry = methodParameters.ObjectDirectoryEntry.Children.Add("OU=" + name, "organizationalUnit");
                    ouDirectoryEntry.CommitChanges();
                    return ouDirectoryEntry;
                });
        }

        /// <summary>
        /// Returns the parent OU properties of the object specifiedi in the filter
        /// </summary>
        /// <param name="objectDN">The object DistinguishedName</param>
        /// <returns>the parent OrganizationalUnit of the object specified/returns>
        public OrganizationalUnit GetParentOU(string objectDN) {
            string domainDN = ADDataAccess.GetDomainDNFromDN(objectDN);
            return GetParentOU(domainDN, "distinguishedName=" + objectDN);
        }

        /// <summary>
        /// Returns the parent OU properties of the object specifiedi in the filter
        /// </summary>
        /// <param name="domain">The domain NetbiosName, DNSName, or DistinguishedName</param>
        /// <param name="filter"></param>
        /// <returns>the parent OrganizationalUnit of the object specified in the filer</returns>
        public OrganizationalUnit GetParentOU(string domain, string filter) {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for domain: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), string.IsNullOrWhiteSpace(domain) ? "NULL" : domain));

            #region Validation
            DomainConfiguration domainInfo = ADDataAccess.ADDomainInfo.Where(x => string.Equals(x.NetbiosName, domain, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (domainInfo == null) domainInfo = ADDataAccess.ADDomainInfo.Where(x => string.Equals(x.DNSName, domain, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (domainInfo == null) domainInfo = ADDataAccess.ADDomainInfo.Where(x => string.Equals(x.DistinguishedName, domain, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if ((domainInfo == null) || string.IsNullOrWhiteSpace(domainInfo.DistinguishedName)) {
                throw new ApplicationException(string.Format("Information not found for domain: {0}", domain != null ? domain : "NULL"));
            }
            #endregion

            MethodParameters methodParameters = new MethodParameters() {
                BaseDN = domainInfo.DistinguishedName,
                ServerNameOrAdjustedBindPath = null,
                Filter = filter,
                DomainInfo = domainInfo,
                PartitionScope = PartitionScope.GlobalCatalog
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, OrganizationalUnit>(
                ref methodParameters,
                (x) => {
                    SearchResult result = ADDataAccess.GetSearchResult(methodParameters);
                    if (result == null) return null;

                    methodParameters.ServerNameOrAdjustedBindPath = result.Path;

                    var directoryEntry = result.GetDirectoryEntry();
                    if ((directoryEntry == null) || (directoryEntry.Parent == null)) return null;
                    if (!string.Equals(directoryEntry.Parent.SchemaClassName, "organizationalUnit", StringComparison.OrdinalIgnoreCase)) return null;

                    var ouProperties = new OrganizationalUnit(directoryEntry.Parent);
                    return ouProperties;
                });
        }
    }
}
